#include "config.h"

#include <errno.h>

#define DBG_SUBSYS S_LIBYNET

#include "ynet_net.h"
#include "ynet_rpc.h"
#include "net_global.h"
#include "fence.h"
#include "dbg.h"

#define POLL_TIME_OUT  (1000 * 2)

void del_all_host(struct list_head *hlist)
{
        struct hostinfo *host;
        int total = 0;
	struct list_head *pos, *n;

	if (hlist->next == hlist)
		return ;

        list_for_each_safe(pos, n,  hlist) {
		list_del(pos);
		host = (struct hostinfo *)pos;
                close(host->fd);

                if(!host->link) {
                        total++;
                	DINFO("host %s noreply, total %u\n", host->hostname, total);
                }

                free(host);
        }
}

void del_all_nic(struct list_head *nlist)
{
        struct niclist *tp;
	struct list_head *pos, *n;

	if (nlist->next == nlist)
		return ;

	list_for_each_safe(pos, n, nlist) {
		list_del(pos);
		tp = (struct  niclist *)pos;
		
		free(tp);
	}
}


static struct hostinfo* __get_host(int fd, struct list_head *hlist)
{
	struct list_head *pos;
	struct hostinfo *host = NULL, *host1;
	list_for_each(pos, hlist) {
		
		host1 = (struct hostinfo *)pos;
		if(host1->fd == fd){
			host = host1;
			break;
		}
	}
	return host;
}	

#ifdef FENCE_USE_ARP
static struct ipinfo *  __cmp_inaddr(struct in_addr rsrc, struct in_addr rdst, struct list_head *hlist)
{
        struct hostinfo *host;
	struct list_head *pos;
        struct ipinfo *ip = NULL;

	
	if (hlist->next == hlist)
		return ip;

        list_for_each(pos, hlist){
		host = (struct hostinfo *)pos;

                if (host->intf[host->cur_index].src.s_addr == rdst.s_addr
                                && host->intf[host->cur_index].dst.s_addr == rsrc.s_addr) {

                        ip = &host->intf[host->cur_index];

                        break;
                }
        }
	
        return ip;
}
#endif
static uint16_t __ping_icmp4_checksum (char *buf, size_t len)
{
        uint32_t sum = 0;
        uint16_t ret = 0;

        uint16_t *ptr;

        for (ptr = (uint16_t *) buf; len > 1; ptr++, len -= 2)
                sum += *ptr;

        if (len == 1)
        {
                *(char *) &ret = *(char *) ptr;
                sum += ret;
        }

        /* Do this twice to get all possible carries.. */
        sum = (sum >> 16) + (sum & 0xFFFF);
        sum = (sum >> 16) + (sum & 0xFFFF);

        ret = ~sum;

        return (ret);
}

static int __recv_icmp_pack(char *buffer, size_t buffer_len, int host_ident, int host_seq)
{

	struct ip *ip_hdr;
	struct icmp *icmp_hdr;	

	size_t ip_hdr_len;

	uint16_t recv_checksum;
	uint16_t calc_checksum;

	uint16_t ident;
	uint16_t seq;


	if (buffer_len < sizeof (struct ip))
		return -1;

	ip_hdr     = (struct ip *) buffer;
	ip_hdr_len = ip_hdr->ip_hl << 2;

	if (buffer_len < ip_hdr_len)
		return -1;

	buffer     += ip_hdr_len;
	buffer_len -= ip_hdr_len;

	if (buffer_len < sizeof (struct icmp))
		return -1;

	icmp_hdr = (struct icmp *) buffer;
	buffer     += sizeof (struct icmp);
	buffer_len -= sizeof (struct icmp);

	if (icmp_hdr->icmp_type != ICMP_ECHOREPLY)
	{
		DBUG("Unexpected ICMP type: %i and code is %i\n", icmp_hdr->icmp_type, icmp_hdr->icmp_code);
		return -1;
	}

	recv_checksum = icmp_hdr->icmp_cksum;
	icmp_hdr->icmp_cksum = 0;
	calc_checksum = __ping_icmp4_checksum ((char *) icmp_hdr,
			sizeof (struct icmp) + buffer_len);

	if (recv_checksum != calc_checksum)
	{
		DWARN("Checksum missmatch: Got 0x%04"PRIx16", "
				"calculated 0x%04"PRIx16"\n",
				recv_checksum, calc_checksum);
		return -1;
	}

	ident = ntohs (icmp_hdr->icmp_id);
	seq   = ntohs (icmp_hdr->icmp_seq);
	
	if(ident != host_ident || seq != host_seq){
		return -1;
	}
	
	return 0;
}

static int __recv_pack(int fd, int ident, int seq)
{
	struct msghdr msghdr;
	struct cmsghdr *cmsg;
	char payload_buffer[4096];
	ssize_t payload_buffer_len;
	char control_buffer[4096];
	struct iovec payload_iovec;

	memset (&payload_iovec, 0, sizeof (payload_iovec));
	payload_iovec.iov_base = payload_buffer;
	payload_iovec.iov_len = sizeof (payload_buffer);

	memset (&msghdr, 0, sizeof (msghdr));
	/* unspecified source address */
	msghdr.msg_name = NULL;
	msghdr.msg_namelen = 0;
	/* output buffer vector, see readv(2) */
	msghdr.msg_iov = &payload_iovec;
	msghdr.msg_iovlen = 1;
	/* output buffer for control messages */
	msghdr.msg_control = control_buffer;
	msghdr.msg_controllen = sizeof (control_buffer);
	/* flags; this is an output only field.. */
	msghdr.msg_flags = 0;

	payload_buffer_len = recvmsg (fd, &msghdr, /* flags = */ 0);
	if (payload_buffer_len < 0)
	{
		return (-1);
	}
	int recv_ttl = -1;
	uint8_t recv_qos = 0;
	for (cmsg = CMSG_FIRSTHDR (&msghdr);cmsg != NULL;
			cmsg = CMSG_NXTHDR (&msghdr, cmsg))
	{
		if(cmsg->cmsg_level != IPPROTO_IP)
			continue;
		if (cmsg->cmsg_type == IP_TOS)
		{
			memcpy (&recv_qos, CMSG_DATA (cmsg),
					sizeof (recv_qos));
		} else if (cmsg->cmsg_type == IP_TTL)
		{
			memcpy (&recv_ttl, CMSG_DATA (cmsg),
					sizeof (recv_ttl));
		}
		else
		{
			 DWARN( "Not handling option %i.\n",cmsg->cmsg_type);
			
			return -1;
		}
		
	}
	int ret;
	ret = __recv_icmp_pack(payload_buffer, payload_buffer_len, ident, seq);
		
	return ret;
	
}
static int __send_pack(int fd,  struct in_addr dst, int ident, int sequence)
{
	  struct icmp *icmp4;
        int status;
	struct sockaddr_in sa;

        char buf[4096];
	char time_buf[32];
        int  buflen;

        char *data;
        int   datalen;


	time_t time_sec_val;
	struct tm *tmp, t;

	time_sec_val = gettime();
	tmp = localtime_safe(&time_sec_val, &t);
	if (tmp == NULL) {
		DWARN("localtime call fail\n");		
	} else {

		if (strftime(time_buf, sizeof(time_buf), "%Y %m %d %T", tmp) == 0) {
			DWARN("strftime returned 0\n");
			memset((void *)time_buf, 0x00, 32);		
		}
	}
	
	bzero(&sa, sizeof(struct sockaddr_in));	
	sa.sin_addr = dst;
	sa.sin_family = AF_INET;
        memset (buf, '\0', sizeof (buf));
        icmp4 = (struct icmp *) buf;
        data  = (char *) (icmp4 + 1);

        icmp4->icmp_type  = ICMP_ECHO;
        icmp4->icmp_code  = 0;
        icmp4->icmp_cksum = 0;
        icmp4->icmp_id    = htons (ident);
        icmp4->icmp_seq   = htons (sequence);

        buflen = 4096 - sizeof (struct icmp);
        snprintf(data, buflen,  "lich icmp request for fence and  the request  time is %s\n", time_buf);
        datalen = strlen (data);

        buflen = datalen + sizeof (struct icmp);

        icmp4->icmp_cksum = __ping_icmp4_checksum (buf, buflen);

        DBUG("Sending ICMPv4 package with ID %d\n", ident);

        status =  sendto (fd, buf, buflen, 0, (struct sockaddr *) &sa, sizeof(sa));
        if (status < 0)
        {
                DWARN("sendto error\n");
                return (-1);
        }


        return 0;
}

int ping_get_ident (void)
{
	int fd;

	int retval;

	if ((fd = open ("/dev/urandom", O_RDONLY)) != -1)
	{
		unsigned int seed;

		if (read (fd, &seed, sizeof (seed)) != -1)
		{
			srandom (seed);
		}

		close (fd);
	} else {
		return -1;
	} 

	retval = (int) random ();

	return (retval);
}

int handle_icmp_packs(struct list_head *hlist, int total)
{
	unsigned int ret, fdcount ;
	int  ok = 1;
	struct ipinfo *ip;
	struct hostinfo *host;
	struct list_head *pos;
	struct pollfd fds[MAX_NIC_NUM];
	struct timeval oldtv;
        uint64_t used;

        ANALYSIS_BEGIN(0);
        
	fdcount = 0;

	list_for_each(pos, hlist) {

		host = (struct hostinfo *)pos;

		host->link = 0;

		host->cur_index++;
		if (host->cur_index >= host->nic_num - 1) {
			host->cur_index = 0;
		}
		
		host->seq++;
		DBUG("the host index is %d nic num is %d\n", host->cur_index, host->nic_num);
		ip = &host->intf[host->cur_index];


		DBUG("begin send to --------------------%s--\n", host->hostname);
		ret = __send_pack(host->fd, ip->dst, host->ident, host->seq);
		if (unlikely(ret)) {
			DWARN("fence icmp send eroor\n");
			continue;
		}

		fds[fdcount].fd = host->fd;
		fds[fdcount++].events = POLLIN;
		ip->nic->status++;
	}

	gettimeofday(&oldtv, NULL);

	while(1) {
		unsigned int i;
		int ret;
		struct hostinfo *hst = NULL;

		ret = poll(fds, fdcount, POLL_TIME_OUT);
		if (ret == 0) {
                        used = _time_used2(&oldtv);
                        if (used  / 1000 > POLL_TIME_OUT) {
                                break;
                        }

                        continue;
		}

		for (i = 0; i < fdcount; i++) {
			if (fds[i].revents & POLLIN) {
				int fd = fds[i].fd;

				hst = __get_host(fd, hlist);
				if(hst == NULL){
					DERROR("fd is not valid %d\n", fd);
				}
				
				ret = __recv_pack(fd, hst->ident, hst->seq);
				if (unlikely(ret)) {
					DBUG("return value is %d host is %s\n",ret, hst->hostname);
				} else {
					if(hst->link == 0){
						ok++;
						hst->link = 1;
						DBUG("recv from %s and ok is %d\n", hst->hostname, ok);
					}
				}
			}
		}

		if (ok == total) {
			break;
                }

                used = _time_used2(&oldtv);
                if (used  / 1000 > POLL_TIME_OUT) {
                        break;
                }
	}

        ANALYSIS_END(0, 1 * 500 * 1000, NULL);
        
	return ok;
}

static unsigned long __addr(struct in_addr *in_addr)
{
        unsigned int *saddr = (void *)in_addr;

        return *saddr;
}

int  get_nic_info(struct list_head *nlist)
{
        struct niclist *tp;
        FILE *fp;
        int nlen, ret = 0;
        char line[256], *ptr, dev[16] = {0};
        struct ifreq ifr;
        struct  sockaddr_in ip_addr, netmask;
        char *tmp;

        int sfd = socket(AF_INET, SOCK_DGRAM, 0),fd;

retry:
        fp = fopen("/proc/net/dev", "r");
	if(fp == NULL){
		goto retry;				
	}

        tmp = fgets(line, 256, fp);
        tmp = fgets(line, 256, fp);
        (void) tmp;

        while(fgets(line, 256, fp) != NULL){
#ifdef  DISABLE_BRIDGE
                if (strstr(line, "br") || strstr(line, "lo"))
                        continue;
#else 
                if (strstr(line, "lo"))
                        continue;
#endif

                ptr = strstr(line, ":");

                nlen = ptr - line;

                int i = 0;
                while(line[i] == ' ') {
                        nlen--;
                        i++;
                }

                memset(dev, 0, 16);
                strncpy(dev , line + i, nlen);
                strcpy(ifr.ifr_name, dev);

                tp = malloc(sizeof(struct niclist));

                memset(tp, 0x00, sizeof(struct niclist));

                strcpy(tp->name, dev);

                if (ioctl(sfd, SIOCGIFADDR, &ifr) < 0) {
                        DBUG("get %s ip addr failed \n", dev);
                        free(tp);
                        continue;
                }

                memcpy(&ip_addr, &ifr.ifr_addr, sizeof(ip_addr));
                //tp->ipaddr = *(unsigned long*)&(ip_addr.sin_addr);
                tp->ipaddr = __addr(&ip_addr.sin_addr);

                if (ioctl(sfd, SIOCGIFNETMASK, &ifr) < 0) {
                        DWARN("get %s netmask failed \n", dev);                
                        free(tp);
                        continue;
                }

                memcpy(&netmask, &ifr.ifr_netmask, sizeof(netmask));
                //tp->netmask = *(unsigned long*)&(netmask.sin_addr);
                tp->netmask = __addr(&netmask.sin_addr);

                //tp->netaddr = *(unsigned long*)&(ip_addr.sin_addr) & *(unsigned long*)&(netmask.sin_addr);
                tp->netaddr = __addr(&ip_addr.sin_addr) & __addr(&netmask.sin_addr);

                fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
                if (fd == -1) {
                        free(tp);

                        ret = errno;
                        DWARN("fence icmp %s socket error %s\n", dev, strerror(ret));
                        continue;
                }


                ret = ioctl(fd, SIOCGIFINDEX, &ifr);
                if (ret == -1) {        

                        close(fd);
                        free(tp);

                        ret = errno;
                        DWARN("fence icmp %s ioctl ifindex error %s\n", dev, strerror(ret));

                        continue;
                }

                tp->ifindex = ifr.ifr_ifindex;

                if (ioctl(fd, SIOCGIFFLAGS, (char*)&ifr)) {
                        close(fd);
                        free(tp);

                        DWARN("fence icmp %s ioctl ifflag error\n", dev);
                        continue;
                } 

                if (!(ifr.ifr_flags&IFF_UP)) {
                        DWARN("fence icmp interface %s is down \n", dev);

                        close(fd);
                        free(tp);

                        continue;                         
                }


		DBUG("nic %s is on the line \n", tp->name);                
		list_add_tail(&tp->list, nlist);
		close(fd);
        }

        fclose(fp);
        close(sfd);

        if (nlist->next == nlist) {
                ret = EPERM;
                GOTO(err_ret, ret);
        }

        return 0;

err_ret:
        return ret;
}

static struct in_addr __in_addr(unsigned long *addr)
{
        struct in_addr *in_addr = (void *)addr;

        return *in_addr;
}

struct niclist* get_src_ip(struct in_addr  dst_ip, struct in_addr * src_ip, struct list_head *nlist,char devname[])
{
        unsigned long netaddr, netmask;
	struct list_head *pos;
	struct niclist *nic, *tmp = NULL;
	
        DBUG("dst ip %s \n", inet_ntoa(dst_ip));                     
        list_for_each(pos, nlist){
		nic = (struct niclist *)pos;

                netmask = nic->netmask;
                netaddr = nic->netaddr;
        	//DBUG("src ip %s \n", inet_ntoa(*(struct in_addr *)&nic->ipaddr));                     

                //if (netaddr == (*(unsigned long *)&dst_ip & netmask)) {
                if (netaddr == (__addr(&dst_ip) & netmask)) {

                        //*src_ip = *(struct in_addr *)&nic->ipaddr;
                        *src_ip = __in_addr(&nic->ipaddr);

                        strcpy(devname, nic->name);
                        tmp = nic;

                        break;
                }
        }

        return tmp;
}
